package lumis.portal.css.serialization;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import lumis.portal.PortalException;
import lumis.portal.PortalContext.RelativePathType;
import lumis.portal.css.CssConfig;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.progress.ProgressConfig.Type;
import lumis.portal.serialization.BaseSerializationContext;
import lumis.portal.serialization.DeserializationContext;
import lumis.portal.serialization.FileCopyTransactionObserver;
import lumis.portal.serialization.SerializationContext;
import lumis.portal.serialization.SerializationException;
import lumis.portal.serialization.persistency.IDirectory;
import lumis.util.FileUtil;
import lumis.util.PortalUtil;

/**
 * Manager for Css serialization
 *
 * @version $Revision: 12710 $ $Date: 2011-03-18 17:00:16 -0300 (Fri, 18 Mar 2011) $
 * @since 4.0.7
 */
public class CssSerializationManager implements ICssSerializationManager
{
	private final String configAlias = "css";
	
	// DESERIALIZATION
	
	public void deserialize(DeserializationContext deserializationContext, CssSerializableConfig cssSerializableConfig) throws PortalException
	{
		deserializationContext.logDebug("Deserializing CSS + '" + cssSerializableConfig.getId() + "'");

		// deserialize config
		CssConfig cssConfig = ManagerFactoryInternal.getSerializationManager2().deserializeObject(CssConfig.class, deserializationContext.getDirectory(), getConfigPath(deserializationContext, cssSerializableConfig.getId()), configAlias, false);
		
		// determine file paths
		String sourceFilePath = getCssFilePath(deserializationContext, cssSerializableConfig.getId());
		String destinationFileRelativePath = cssConfig.getFilePath();
		String destinationFileAbsolutePath = RelativePathType.WWW.getAbsolutePath(destinationFileRelativePath);
		File destinationFile = new File(destinationFileAbsolutePath);
		IDirectory directory = deserializationContext.getDirectory();

		// add config if it not already exists
		boolean deserializeCssFile = false;
		CssConfig existingCssConfig = ManagerFactory.getCssManager().get(deserializationContext.getSessionConfig(), cssConfig.getId(), deserializationContext.getTransaction());
		if (existingCssConfig == null)
		{
			if (!destinationFile.exists())
			{
				deserializationContext.logDebug(" CSS does not exist. Adding:", cssConfig);
				deserializeCssFile = true;
			}
			else // conflict
			{
				deserializationContext.logDebug(" CSS does not exist, but its file exists. Resolving file conflict:", cssConfig);
				switch(deserializationContext.getExistingStyleAction())
				{
					case ABORT:
						deserializationContext.sendProgressNotification("STR_CSS_FILE_ALREADY_EXISTS;" + destinationFileRelativePath, Type.ERROR, null);
						cssConfig.setFilePath(PortalUtil.generateNewGuid());
						deserializationContext.setMustRollBack();
						break;
					case SKIP:
						deserializationContext.sendProgressNotification("STR_MAINTAINING_CSS;" + destinationFileRelativePath, Type.WARNING, null);
						break;
					case UPDATE:
						deserializationContext.sendProgressNotification("STR_UPDATING_CSS_FILE;" + destinationFileRelativePath, Type.WARNING, null);
						deserializeCssFile = true;
						break;
				}
			}
			ManagerFactory.getCssManager().add(deserializationContext.getSessionConfig(), cssConfig, deserializationContext.getTransaction());
		}
		else
		{
			try
			{
				deserializationContext.logDebug(" CSS already exists. Resolving conflict.");

				// a file conflict exists when the configs or their referenced file contents are different
				// (absent destination file implies content difference)
				// when there is no conflict, no update is needed
				if (!cssConfig.equals(existingCssConfig) || !compareFiles(deserializationContext.getDirectory(), sourceFilePath, destinationFile)) // conflict
				{
					String existingCssName = existingCssConfig.getName();
					String existingCssPath = existingCssConfig.getFilePath();
					String existingCssFinalName = "'" + existingCssName + "' (" + existingCssPath + ")";
					String newCssName = cssConfig.getName();
					String newCssPath = cssConfig.getFilePath();
					String newCssFinalName = "'" + newCssName + "' (" + newCssPath + ")";
					String message;
					switch(deserializationContext.getExistingStyleAction())
					{
						case ABORT:
							if (newCssName.equals(existingCssName) && newCssPath.equals(existingCssPath))
								message = "STR_CSS_ALREADY_EXISTS;" + existingCssFinalName;
							else
								message = "STR_CSS_CONFLICTS_TO;" + existingCssFinalName + ";" + newCssFinalName;
							deserializationContext.sendProgressNotification(message, Type.ERROR, null);
							deserializationContext.setMustRollBack();
							break;
						case UPDATE:
							if (newCssName.equals(existingCssName) && newCssPath.equals(existingCssPath))
								message = "STR_UPDATING_CSS;" + existingCssFinalName;
							else
								message = "STR_UPDATING_CSS_TO;" + existingCssFinalName + ";" + newCssFinalName;
							deserializationContext.sendProgressNotification(message, Type.WARNING, null);
							ManagerFactory.getCssManager().update(deserializationContext.getSessionConfig(), cssConfig, deserializationContext.getTransaction());
							deserializeCssFile = true;
							break;
						case SKIP:
							if (newCssName.equals(existingCssName) && newCssPath.equals(existingCssPath))
								message = "STR_MAINTAINING_CSS;" + existingCssFinalName;
							else
								message = "STR_MAINTAINING_CSS_IGNORING;" + existingCssFinalName + ";" + newCssFinalName;
							deserializationContext.sendProgressNotification(message, Type.WARNING, null);
							break;
					}
				}
			}
			catch (IOException e)
			{
				throw new SerializationException("STR_ERROR_ANALYZING_CSS;" + cssConfig.getName() + ";" + destinationFileRelativePath, e);
			}
		}

		// deserialize css file
		boolean sourceFileExists;
		try
		{
			sourceFileExists = directory.exists(sourceFilePath);
		}
		catch (IOException e)
		{
			throw new SerializationException("STR_ERROR_ANALYZING_CSS;" + cssConfig.getName() + ";" + destinationFileRelativePath, e);
		}
		if (!sourceFileExists)
			deserializationContext.sendProgressNotification("STR_SOURCE_CSS_NOT_FOUND;" + sourceFilePath, Type.WARNING, null);
		if (deserializeCssFile && sourceFileExists)
		{
			deserializationContext.logDebug(" Adding FileCopyTransactionObserver: sourcePath:DATA\\" + sourceFilePath + " destinationPath:WWW\\" + destinationFileRelativePath);
			FileCopyTransactionObserver fileCopyTransactionObserver = new FileCopyTransactionObserver(deserializationContext.getSessionConfig(), sourceFilePath, deserializationContext.getDirectory(), destinationFileRelativePath, RelativePathType.WWW, deserializationContext.getTransaction());
			deserializationContext.getTransaction().addObserver(fileCopyTransactionObserver);
		}
	}

	private boolean compareFiles(IDirectory sourceDirectory, String sourceFilePath, File destinationFile) throws IOException
	{
		boolean sourceFileExists = sourceDirectory.exists(sourceFilePath);
		if (sourceFileExists != destinationFile.exists()) // there are differences 
			return false;
		if (!sourceFileExists) // neither source or destination exist
			return true;
		InputStream sourceStream = sourceDirectory.getInputStream(sourceFilePath);
		try
		{
			InputStream in2 = new FileInputStream(destinationFile);
			try
			{
				return FileUtil.compareStream(sourceStream, in2);
			}
			finally
			{
				in2.close();				
			}
		}
		finally
		{
			sourceStream.close();
		}
	}

	// SERIALIZATION
	
	public CssSerializableConfig serialize(SerializationContext serializationContext, String cssId) throws PortalException
	{
		// get config
		CssConfig cssConfig = ManagerFactory.getCssManager().get(serializationContext.getSessionConfig(), cssId, serializationContext.getTransaction());

		// serialize config
		ManagerFactoryInternal.getSerializationManager2().serializeObject(cssConfig, serializationContext.getDirectory(), getConfigPath(serializationContext, cssConfig.getId()), configAlias);

		// serialize css file
		serializeCssFile(serializationContext, cssConfig);
		
		// build structure object
		return createSerializableConfig(cssConfig);
	}

	private CssSerializableConfig createSerializableConfig(CssConfig cssConfig)
	{
		CssSerializableConfig cssSerializableConfig = new CssSerializableConfig();
		cssSerializableConfig.setId(cssConfig.getId());
		cssSerializableConfig.setName(cssConfig.getName());
		return cssSerializableConfig;
	}
	
	private void serializeCssFile(SerializationContext serializationContext, CssConfig cssConfig) throws SerializationException
	{
		File srcFile = new File(RelativePathType.WWW.getAbsolutePath(cssConfig.getFilePath()));
		InputStream inputStream = null;
		OutputStream outputStream = null;
		try
		{
			inputStream = new FileInputStream(srcFile);
			outputStream = serializationContext.getDirectory().getOutputStream(getCssFilePath(serializationContext, cssConfig.getId())); 
			FileUtil.copyStream(inputStream, outputStream);
		}
		catch (IOException e)
		{
			throw new SerializationException("STR_ERROR_SERIALIZING_CSS_FILE;" + cssConfig.getFilePath(), e);
		}
		finally
		{
			try
			{
				if (inputStream != null)
					inputStream.close();
			}
			catch (IOException e)
			{
				serializationContext.logDebug("Error closing file copy inputStream", e);
			}
			try
			{
				if (outputStream != null)
					outputStream.close();
			}
			catch (IOException e)
			{
				serializationContext.logDebug("Error closing file copy outputStream", e);
			}
		}
	}
	
	// GENERAL
	
	public void setupSerializationContext(BaseSerializationContext serializationContext)
	{
		serializationContext.getSerializationSettings().getClassAliases().put(CssSerializableConfig.class, "css");
	}

	/**
	 * Get the full path for the serialization file.
	 * @param deserializationContext
	 * @param configId
	 * @return
	 */
	private String getConfigPath(BaseSerializationContext deserializationContext, String configId)
	{
		return "csss/" + configId + ".xml";
	}

	/**
	 * Get the full path for the CSS file.
	 * @param deserializationContext
	 * @param configId
	 * @return
	 */
	private String getCssFilePath(BaseSerializationContext deserializationContext, String configId)
	{
		return "csss/" + configId + ".css";
	}

}
